package cn.ycc1.functionlibrary.reflection;

/**
 * Working with Enumerations
 * @author ycc
 * @date 2025/3/9
 * Since enums are classes, reflection has no need to define an explicit java.lang.reflect.Enum class, and indeed, there is none.
 * The only Reflection APIs that are specific to enums are Class.isEnum(), Class.getEnumConstants(), and Field.isEnumConstant().
 * Most reflective operations involving enums are the same as any other class or member. For example, enum constants are
 * implemented as public static final fields on the enum. The following sections show how to use Class and Field with enums.
 */
public class WorkingWithEnumerations {
    /**
     * Getting Access to the Class and the Constants
     * Reflection provides three enum-specific APIs:
     *
     * Class.isEnum(): indicates whether this class represents an enum type.
     * Class.getEnumConstants(): retrieves the list of enum constants defined by the enum in the order they are declared.
     * Field.isEnumConstant(): indicates whether this field represents an element of an enumerated type.
     * Sometimes it is necessary to dynamically retrieve the list of enum constants; in non-reflective code this is accomplished
     * by invoking the implicitly declared static method values() on the enum. If an instance of an enum type is not available
     * the only way to get a list of the possible values is to invoke Class.getEnumConstants() since it is impossible to
     * instantiate an enum type.
     *
     * Suppose that you have the following enum.
     *
     * public enum Days {
     *     SATURDAY, SUNDAY
     * }
     * Then you can write the following code to reflectively get access to different elements of this enum.
     *
     * Class<?> c = Days.class;
     * System.out.println("class: " + c.getName());
     *
     * System.out.println("values: " + Arrays.toString(Days.values()));
     * System.out.println("constants: " + Arrays.toString(c.getEnumConstants()));
     * Running the previous code prints the following. As you can see, the call to c.getEnumConstants() returns the same array as
     * the one you get with Days.values().
     *
     * class: org.devjava.Days
     * values: [SATURDAY, SUNDAY]
     * constants: [SATURDAY, SUNDAY]
     */

    /**
     * Locating Enum Constants
     * Thanks to the Reflection API, you can locate the constructors of this Days enum.
     *
     * Class<Days> c = Days.class;
     * Constructor<?>[] constructors = c.getDeclaredConstructors();
     * int length = constructors.length;
     * System.out.println("# constructors = " + length);
     * System.out.println("constructors[0]: " + constructors[0]);
     * Running the previous code gives you the following. As you can see, your Days enum has one constructor, which is private,
     * and takes two parameters:
     *
     * a String, which is the name of a constant
     * and an int, which is the index of this constant in the array of the constants this enum defines.
     * # constructors = 1
     * constructors[0]: private org.devjava.Days(java.lang.String,int)
     * You can then try to get a reference on this constructor, and invoke it to create another constants in the Days enum.
     *
     * Class<Days> c = Days.class;
     * Constructor<Days> constructor = c.getDeclaredConstructor(String.class, int.class);
     * System.out.println("constructor = " + constructor);
     *
     * constructor.setAccessible(true);
     * Days monday = constructor.newInstance("MONDAY", 2);
     * Running this code prints the following.
     *
     * constructor = private org.devjava.Days(java.lang.String,int)
     * Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
     *     at java.base/java.lang.reflect.Constructor.acquireConstructorAccessor(Constructor.java:547)
     *     at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
     *     at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)
     *     at org.devjava.Main.main(ScrapMain.java:42)
     * Indeed, it is not possible to add constants to an enum at runtime, even using the Reflection API.
     */

    /**
     * Getting Access to Members
     * Suppose now that you have the following enum. Technically speaking we know that the Sun is not a planet, but the people
     * who named the days of the week believed so at the time.
     *
     * public enum Days {
     *     SATURDAY("Saturn"), SUNDAY("Sun");
     *
     *     private final String planet;
     *
     *     Days(String planet) {
     *         this.planet = planet;
     *     }
     *
     *     public static Days of(String label) {
     *         return Arrays.stream(values())
     *                 .filter(day -> day.planet.equals(label))
     *                 .findAny().orElseThrow();
     *     }
     *
     *     public String planet() {
     *         return this.planet;
     *     }
     * }
     * Note that this enum as a defined constructor, a field, and a method, which is an accessor to this field. It also has a
     * factory method, to get one of the constant from a label.
     *
     * You can discover the number of constructors this Days enum has.
     *
     * Class<Days> c = Days.class;
     * Constructor<?>[] constructors = c.getDeclaredConstructors();
     * int length = constructors.length;
     * System.out.println("# constructors = " + length);
     * Running the previous code prints the following. This enum still has only one constructor: the one you defined. But as
     * you can see, it takes more parameters than the one you defined. The compiler added the name of this constant, and its
     * index. It is still not possible to invoke this constructor reflectively to create more constants at runtime.
     *
     * # constructors = 1
     * constuctor[0]: private org.devjava.Days(java.lang.String,int,java.lang.String)
     * All the other members of your enum can be accessed reflectively, as any member of any regular class.
     */

    /**
     * Discovering Compiler Generated Fields
     * The Reflection API can give you access to the internal structure of the Days class that the compiler generated for you.
     *
     * Let us start with the fields. Some of them are static, so to get their value you need to call field.get(null), because
     * a static field doesn't depend on any object. And some of them are arrays. Thus, the special processing in the following
     * code.
     *
     * Class<Days> c = Days.class;
     * Field[] fields = c.getDeclaredFields();
     *
     * for (Field field : fields) {
     *     System.out.println(field);
     *     if (Modifier.isStatic(field.getModifiers())) {
     *         field.setAccessible(true);
     *         if (field.getType().isArray()) {
     *             System.out.println("  " + Arrays.toString((Object[])field.get(null)));
     *             System.out.println("    is enum constant? " + field.isEnumConstant());
     *         } else {
     *             System.out.println("  " + field.get(null));
     *             System.out.println("    is enum constant? " + field.isEnumConstant());
     *         }
     *     }
     * }
     * Running the previous code prints the following. There are several points worth mentioning.
     *
     * The compiler creates one public static field for each constant that you define in your enum, with the name of this
     * constant.
     * It also creates a private static array called $VALUES, with all the constants you defined in it. This array is returned
     * by the Days.values() call.
     * public static final org.paumard.ScrapMain$Days org.paumard.ScrapMain$Days.SATURDAY
     *   SATURDAY
     *     is enum constant? true
     * public static final org.paumard.ScrapMain$Days org.paumard.ScrapMain$Days.SUNDAY
     *   SUNDAY
     *     is enum constant? true
     * private static final org.paumard.ScrapMain$Days[] org.paumard.ScrapMain$Days.$VALUES
     *   [SATURDAY, SUNDAY]
     *     is enum constant? false
     */

    /**
     * Discovering Compiler Generated Methods
     * You can also discover the methods that the compiler created for you.
     *
     * Class<Days> c = Days.class;
     * Method[] methods = c.getDeclaredMethods();
     * for (Method method : methods) {
     *     System.out.println(method);
     * }
     * Running this code prints the following. You can see the methods created by the compiler.
     *
     * Days.values(): this is the classical method that returns the array of the constants you defined.
     * Days.valueOf(String): this is also a classical method, the one that returns a constant from its name.
     * Days.$values(): this is a private method created by the compiler.
     * The Days.lambda$of$0(String, Days) method corresponds to the predicate created in the of() method.
     *
     * public static org.devjava.Days[] org.devjava.Days.values()
     * public static org.devjava.Days org.devjava.Days.valueOf(java.lang.String)
     * public static org.devjava.Days org.devjava.Days.of(java.lang.String)
     * private static org.devjava.Days[] org.devjava.Days.$values()
     * private static boolean org.devjava.Days.lambda$of$0(java.lang.String,org.devjava.Days)
     * public java.lang.String org.devjava.Days.planet()
     */

}
